Repository navigation
fix(libSceSystemService): park guest threads during LoadExec's shutdown as exit does - #6237
Conversation
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.
|
|
This also fixes the Peace Walker launcher regression reported on #6000 (PPSA18216, same Checked on Ubuntu 26.04 (WSL2), GCC 15.2.0, Release,
AudioLoadExec.cpp#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <string>
#include <thread>
#include <vector>
#include <sys/wait.h>
#include <unistd.h>
extern "C" {
int APS5_VABI sceSystemServiceLoadExec(const char*, const char* const*);
int APS5_VABI sceAudioOut2ContextResetParam(AudioOut2ContextParam*);
int APS5_VABI sceAudioOut2ContextCreate(const AudioOut2ContextParam*, void*, std::size_t, AudioOut2ContextHandle*);
int APS5_VABI sceAudioOut2ContextPush(AudioOut2ContextHandle, std::uint32_t);
int APS5_VABI sceAudioOut2PortCreate(AudioOut2ContextHandle, const AudioOut2PortParam*, AudioOut2PortHandle*);
int APS5_VABI sceAudioOut2PortSetAttributes(AudioOut2PortHandle, const AudioOut2Attribute*, std::uint32_t);
}
namespace {
constexpr char Marker[] = "ANYPS5_TEST_RELAUNCH_MARKER";
std::atomic<int> pushes{0};
std::vector<float> pcm(512, 0.25f);
void Fail(const char* message) {
std::fprintf(stderr, "%s\n", message);
std::fflush(stderr);
std::_Exit(1);
}
std::filesystem::path OwnExecutable() { return std::filesystem::read_symlink("/proc/self/exe"); }
void AppendMarker(const std::string& line) {
const char* marker = std::getenv(Marker);
if (marker == nullptr) Fail("no marker");
std::ofstream(marker, std::ios::app) << line << '\n';
}
void SlowDriverShutdown() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); }
void StartMixer() {
AudioOut2ContextParam params{};
sceAudioOut2ContextResetParam(¶ms);
params.num_grains = 256;
AudioOut2ContextHandle context = 0;
if (sceAudioOut2ContextCreate(¶ms, nullptr, 0, &context) != 0) Fail("context");
AudioOut2PortParam portParams{};
portParams.data_format = 2u << 8;
portParams.sampling_freq = 48000;
AudioOut2PortHandle port = 0;
if (sceAudioOut2PortCreate(context, &portParams, &port) != 0) Fail("port");
std::thread([context, port] {
for (;;) {
const void* pointer = pcm.data();
const AudioOut2Attribute attribute{0, 0, &pointer, sizeof(pointer)};
sceAudioOut2PortSetAttributes(port, &attribute, 1);
sceAudioOut2ContextPush(context, 1);
pushes++;
}
}).detach();
}
int Drive() {
const auto self = OwnExecutable();
const auto secondary = self.parent_path() / ("picker.picker.bin" + self.extension().string());
std::filesystem::copy_file(self, secondary, std::filesystem::copy_options::overwrite_existing);
const auto marker = std::filesystem::temp_directory_path() / ("anyps5-audio-loadexec-" + std::to_string(getpid()) + ".txt");
std::filesystem::remove(marker);
setenv(Marker, marker.c_str(), 1);
const pid_t child = fork();
if (child == 0) {
execl(self.c_str(), self.c_str(), "--launch", static_cast<char*>(nullptr));
std::_Exit(127);
}
int status = 0;
waitpid(child, &status, 0);
std::string text;
for (int attempt = 0; attempt < 100 && text.find("relaunched") == std::string::npos; ++attempt) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::ifstream file(marker);
text.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
}
std::filesystem::remove(marker);
std::filesystem::remove(secondary);
std::printf("launcher: %s %d; marker: %s", WIFEXITED(status) ? "exit" : "signal", WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status), text.empty() ? "(empty)\n" : text.c_str());
if (text != "relaunched\n") Fail("the relaunched executable did not start");
std::puts("Audio LoadExec completed");
return 0;
}
}
int main(int argc, char** argv) {
setenv("SDL_AUDIODRIVER", "dummy", 1);
if (argc == 2 && std::strcmp(argv[1], "--launch") == 0) {
StartMixer();
while (pushes.load() < 3) std::this_thread::sleep_for(std::chrono::milliseconds(1));
LibcRegisterShutdown_nid_postfix(SlowDriverShutdown);
const char* const arguments[] = {"--secondary", nullptr};
sceSystemServiceLoadExec("/app0/picker/picker.bin", arguments);
Fail("LoadExec returned");
}
const int first = argc > 0 && std::strncmp(argv[0], "--", 2) == 0 ? 0 : 1;
if (argc > first && std::strcmp(argv[first], "--secondary") == 0) {
AppendMarker("relaunched");
return 0;
}
return Drive();
}Registered locally next to add_test_executable(audio_loadexec_tests ${LIBS_INCLUDE_DIR}/tests/AudioLoadExec.cpp)
target_include_directories(audio_loadexec_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(audio_loadexec_tests PRIVATE libSceSystemService libSceAudioOut libkernel libc)Not checked: Windows ( AI-assisted: yes (Claude Code). |
|
Thanks for checking the audio path, and for tying it to Peace Walker. I added your run to the description. AI-assisted: yes |
|
Cross-validation on Windows 11 Pro 26220, NVIDIA RTX 4070 (596.21), MinGW GCC 15.2, PR head a03e023 merged on main 8dc92ae: PPSA85300 (VLC 0.3.1). Relinked with
Tests. The 7 A Windows note, not caused by this PR. Each failing negative-control run left a suspended No other concerns. Written with Claude Code assistance. |
|
Thanks for the Windows run and the negative control. I added it to the description, along with the suspended-child case as a follow-up. AI-assisted: yes |
|
Cross-validation on Windows 11 Pro build 26300, Intel i7-11700F, NVIDIA GeForce RTX 3070, MinGW GCC, PR head a03e023 merged onto PPSA99360 (PS5CEMU-HAR 4.0.1) is another launcher that goes through LoadExec. After the pad sequence has let its self-update install into
Other runs of the same title did not reach the restart (the update did not start in three of them, so they say nothing either way) and are not counted. This is only the Windows path for a relaunch with no list, as screamm already covered for VLC; I did not run the macOS or Linux paths. AI-assisted: yes |
|
Thank you @DavidSamir, a second launcher that relaunches itself is exactly the kind of case this needed, and with the AI-assisted: yes |
|
A re-check on current
AI-assisted: yes |
|
Thanks. GitHub shows this as mergeable, but it conflicts in behaviour with #5978 (db3b406), which landed after your last MGS4 run: On main, Please:
A fresh MGS4 run on the rebased branch would also help. |
…wn as exit does Metal Gear Solid 4 (PPSA18194) aborts on main when its launcher starts the game: sceSystemServiceLoadExec runs the guest shutdown, a render thread that then calls into the stopped AGC driver gets ProcessShutdown back from LibcAwaitExit, and the exception unwinds into title code, which terminates the process before the new executable starts. LibcAwaitExit parks a thread only once an exit has been requested, and LoadExec ran the shutdown without requesting one. It now starts the shutdown the way exit does, so such threads wait until the process is replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tarts the exit Since boykopovar#5978, StartExit only parks the other guest threads and exit runs LibcRunShutdown itself, so LoadExec's three exits (the converted executable on Windows and Linux, and the host executable) now call LibcStartExit and then LibcRunShutdown, as exit does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @oneandonlydean, you're right: since #5978, On the test: AI-assisted: yes (Claude Code) |
|
Thanks. That plan sounds right. A red run of |
a03e023 to
e71ade0
Compare
|
Pushed: rebased onto
The description is updated with the same. AI-assisted: yes (Claude Code) |
Peace Walker (PPSA18216): e71ade0 fixes the launcher hand-overSystem: Linux, RTX 3080 Ti (NVIDIA 615.71.09)
AI-assisted; the runs are from my machine. |
|
Thanks @mrfelek, that's a useful second title. Peace Walker goes through the same AI-assisted: yes (Claude Code) |
What
Metal Gear Solid 4 (PPSA18194, 01.004.003) starts in its Master Collection launcher, which runs
/app0/mgs4/mgs4.elfthroughsceSystemServiceLoadExecwhen Start Game is chosen. Onmaind92a8da the process ends right there, before the game starts:With gdb, the aborting thread is one of the title's render threads: it is in
CreateInterpolantMapping→AgcDriverResolveShaderAbi_nid_postfixwhen LoadExec's shutdown stops the driver, and the entry point'scatchcallsLibcAwaitExit_nid_postfix. That function parks the thread only once an exit has been requested (exitRequested).exit,sceSystemServiceKillAppand"exit"request one throughStartExitbefore the shutdown runs; LoadExec calledLibcRunShutdown_nid_postfixdirectly, soLibcAwaitExit_nid_postfixthrewProcessShutdownback into title code, which has nothing to catch it.LoadExec now starts the shutdown the way exit does, on both the
fexecveandCreateProcessWpaths and the argument-less fallback:LibcStartExit_nid_postfix(which isStartExit) and thenLibcRunShutdown_nid_postfix. Since #5978,StartExitno longer runs the shutdown callbacks, so both calls are needed. A thread that reaches a stopped library during it waits until the process is replaced. Thedocs/dev/debt/libSceSystemService.mdentry says so.guest_system_relaunchnow starts a thread that waits inLibcAwaitExit_nid_postfixfrom the first image's shutdown callback, asguest_system_exitdoes forexit.Tested
Ubuntu 24.04, GCC 13.3, Release, on
maind92a8da:guest_system_relaunchand the other sixguest_system_exittests pass 5/5. Withmain'sLoadExec.cpp,Export.cpp,Process.cppandShutdown.hppand the new check,guest_system_relaunchfails 3/3 (Guest thread resumed during exit).^guest_|loadexec|shutdownpass, includingloadexec_argumentsand the AGC, AudioOut and job-manager shutdown tests.--to-intel --executable mgs4/mgs4.elfand run with Cross pressed every 1.5 s (RTX 4070 Ti, NVIDIA 595.99): onmainit ends at Start Game with the lines above. With this change the launcher's process is replaced,mgs4.elfopens its save data and Vulkan device, and it plays its opening (the logo, then the TV channel intro) at about 60 FPS until the 180 s limit. A 600 s run with Cross pressed every 1.5 s also reaches its limit, with no driver failure or unhandled exception in the game's log. With other menu presses, onmainb4a4fca with this PR and feat(libSceWebBrowserDialog): implement sceWebBrowserDialogOpenForPredeterminedContent #5904 (which the launcher's Online Manual button needs), a 600 s run gets past the TV intro into Act 1's opening cutscene, at about 4 to 13 FPS there, and reaches the limit.plat_restartcallssceSystemServiceLoadExec("/app0/eboot.bin", NULL)(platform_ps5.c); a pad sequence that reaches it does so 18 s in. Onmainthe process aborts there withunhandled exception '15ProcessShutdown', 5 of 5 runs (4 on d92a8da, 1 on 0afa6a6). With temporary prints (not part of this PR), the call reachesLibcRunShutdownwithexitRequestedfalse. With this PR the shutdown runs with it set and the process is replaced (2 of 2 runs, plus 1 traced). The relaunched image's output only shows up with fix(libSceSystemService): give LoadExec's next executable the host standard error #5699 as well: with both, it reloads the pipeline cache, hits the shader cache and presents frames until the 60 s limit, 2 of 2 runs.main489466d. A test process keeps an AudioOut2 mixer thread pushing through LoadExec's shutdown: onmainthe image is relaunched 0 of 10 times (the sameProcessShutdownterminate), with this PR 30 of 30. They trace the Metal Gear Solid: Peace Walker launcher regression from fix(libSceAudioOut): park guest audio calls at exit before the port tables are destroyed #6000 (PPSA18216) to this path. Their test is in that comment if a regression test for the audio path is wanted here.main8dc92ae. VLC's restart takes the null-listCreateProcessWpath there. Onmainit ends in the sameProcessShutdownterminate; with this PR the new process starts and presents to the 120 s limit (2 of 2 runs, and 2 more with fix(libSceSystemService): give LoadExec's next executable the host standard error #5699). Theguest_system_exittests andloadexec_argumentspass 3 of 3, and the negative control fails as here. They also note a Windows edge case for a follow-up: a LoadExec with an argument list while another thread is already inexitparks here, and leaves the suspended child behind.main82ddae7. PS5CEMU-HAR 4.0.1 (PPSA99360) restarts itself through LoadExec after its self-update. Onmainit ends in the same unhandledProcessShutdownterminate (exit 0xC0000409). With this PR, 2 of 2 runs that reach the restart exit 0, and the new instance starts.tools/check_conventions.py --base upstream/mainpasses, andtools/pr_overlap.py --branch fix/loadexec-exit-shutdownfinds no overlap. fix(libc): skip guest exit handlers on host window close #5843 and fix(libc): run guest finalizers before driver shutdown #5978 change other lines ofProcess.cpp(the exit paths), not LoadExec.Rebased onto
main864729d (5b072bc), plus e71ade0 for theLibcRunShutdowncalls, after @oneandonlydean's review. Ubuntu 24.04, GCC 13.3, Release:The nine exit and relaunch tests (
guest_system_exit*,guest_system_relaunch,guest_requested_exit,guest_quick_exit,guest_immediate_exit,guest_return_from_main,guest_nested_exit,guest_exit_finalizers_before_shutdown) pass 3 of 3.With 5b072bc's
Export.cppandLoadExec.cpp(noLibcRunShutdownafterLibcStartExit),guest_system_relaunchfails and the other eight pass. It expectsshutdownandsecondary shutdownfrom the callbacks each image registers before its LoadExec.ctest -R '^guest_|libc|loadexec': 402 of 402 pass.MGS4 (PPSA18194), 300 s with pad input. MGS4 also needs fix(libc): give the scripting heap hooks blocks from libc's own heap #6842 (scripting heap), so both builds have it. On
main+ fix(libc): give the scripting heap hooks blocks from libc's own heap #6842 + this PR, the launcher's LoadExec startsmgs4/mgs4.elfand the game runs to the limit. Onmain+ fix(libc): give the scripting heap hooks blocks from libc's own heap #6842 alone, it ends at 43 s with[libc] unhandled exception '15ProcessShutdown'andterminate.Metal Gear Solid: Peace Walker (PPSA18216, run by @mrfelek): Linux, RTX 3080 Ti,
mainc9e6c45 with this PR and the other PRs it needs, relinked with--executable mgspw/mgspw.elf. Without a hand-over fix,ExitAndStartNewProjecttomgspw.elfends interminate called after throwing an instance of 'ProcessShutdown', aborted fromsceAudioOutOutput'sProcessShutdowncatch. With this PR the new image draws the title and the opening movies and runs to the 240 s limit.Not checked on a console. Windows: see screamm's and DavidSamir's runs above.
Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code